#include "../../core/pch.h"
#include "../../pi/BinarySemaphore.h"
#include "../../pi/Process.h"

/** Shared memory specific variables. */
int segment_id, segment_size, sem_id;
struct info_table* shared_memory;
struct shmid_ds shm_buffer;

/** How much seconds to wait before receiver starts working. */
int receiver_delay;

/** Port for logger listening. */
int logger_port;

/** Port for receiver. */
int receiver_port;
int ipg_port;

/** Number to factor. */
int number;

/** Options for slave process. */
struct option long_options[] =
{ 
	{"number", required_argument, 0, 'a'},
	{"delay", required_argument, 0, 'b'},
	{"recvport", required_argument, 0, 'c'},
	{"logport", required_argument, 0, 'd'},
	{"ipgport", required_argument, 0, 'e'},
	{0, 0, 0, 0}
};

/** Address b1.b2.b3.b4 has to be packed as 0xb1b2b3b4. */
const unsigned int MY_IP = 0xc0a84b2b;

int main(int argc, char* argv[])
{
	if (argc < 9)
	{
		DEBUGS("Usage: init --number <num> --logport <port> --recvport <port> --ipgport <port> [--delay <sec>]");
		exit(STATUS_ERR);
	}

	while (true)
	{
		int option_index = 0;
		int op = getopt_long(argc, argv, "a:b:c:d:e:", long_options, &option_index);
		if (op == NO_MORE_OPTIONS)
			break;

		switch (op)
		{
			case 'a':
				number = atoi(optarg);
				break;
			case 'b':
				receiver_delay = atoi(optarg);
				break;
			case 'c':
				receiver_port = atoi(optarg);
				break;
			case 'd':
				logger_port = atoi(optarg);
				break;
			case 'e':
				ipg_port = atoi(optarg);
				break;
			default:
				exit(STATUS_ERR);
		}
	}

	/**
	 * Allocate shared memory segment.
	 */
	segment_id = shmget(IPC_PRIVATE, SHARED_SEGMENT_SIZE, 
		IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

	/**
	 * Attach the shared memory segment.
	 */
	shared_memory = (struct info_table*) shmat(segment_id, NULL, 0);
	memset(shared_memory, 0, SHARED_SEGMENT_SIZE);
	shared_memory->NUMBER = number;

	/**
	 * Allocate semaphor for this memory segment.
	 */
	BinarySemaphore::Create(sem_id);
	BinarySemaphore::Init(sem_id);

	int logger_pid, sender_pid, receiver_pid, ipgiver_pid;

	/**
	 * Run ip giver.
	 */
	if ((ipgiver_pid = Process::Create()) == 0)
	{
		struct sockaddr_in ipg_addr; 
		memset(&ipg_addr, 0, sizeof(ipg_addr)); 
		ipg_addr.sin_family = AF_INET; 
		ipg_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
		ipg_addr.sin_port = htons(ipg_port); 

		int ipg_sock = socket(AF_INET, SOCK_DGRAM, 0); 
		bind(ipg_sock, (struct sockaddr*) &ipg_addr, sizeof(ipg_addr)); 

		socklen_t len = sizeof(struct sockaddr);
		struct sockaddr_in cliaddr;

		while (true)
		{
			struct timeval tv;
			fd_set readfds;

			tv.tv_sec = 3;
			tv.tv_usec = 0;

			FD_ZERO(&readfds);
			FD_SET(ipg_sock, &readfds);

			int ret = select(ipg_sock+1, &readfds, NULL, NULL, &tv);
			if (ret == SELECT_ERROR)
			{
				DEBUGS("init: error on select (%s)", strerror(errno));
				exit(SELECT_ERROR);
			}

			DEBUGS("init: waiting for slaves to search for master");

			char msg = 0;
			if (FD_ISSET(ipg_sock, &readfds))
			{
				recvfrom(ipg_sock, &msg, sizeof(char), 0, (struct sockaddr*) &cliaddr, &len);
				if (msg == ARE_YOU_NUMFAC)
				{
					msg = I_AM_NUMFAC;
					sendto(ipg_sock, &msg, sizeof(char), 0, (struct sockaddr*) &cliaddr, sizeof(cliaddr));
					sendto(ipg_sock, &MY_IP, sizeof(unsigned int), 0, (struct sockaddr*) &cliaddr, sizeof(cliaddr));
				}
			}
			else
				DEBUGS("init: select timeout");
		}
	}

	/** Start logging process. */
	if ((logger_pid = Process::Create()) == 0)
	{
		char* buf[] = { "logger" , 0, 0, 0, 0 };
		buf[1] = (char*) malloc(32 * sizeof(char));
		buf[2] = (char*) malloc(32 * sizeof(char));
		buf[3] = (char*) malloc(32 * sizeof(char));
		sprintf(buf[1], "%d", segment_id);
		sprintf(buf[2], "%d", sem_id);
		sprintf(buf[3], "%d", logger_port);
		execv("logger", buf);
	}

	sleep(receiver_delay);

	/**
	 * Run RECEIVER.
	 */
	if ((sender_pid = Process::Create()) == 0)
	{
		char* buf[] = { "receiver" , 0, 0, 0, 0 };
		buf[1] = (char*) malloc(32 * sizeof(char));
		buf[2] = (char*) malloc(32 * sizeof(char));
		buf[3] = (char*) malloc(32 * sizeof(char));
		sprintf(buf[1], "%d", segment_id);
		sprintf(buf[2], "%d", sem_id);
		sprintf(buf[3], "%d", receiver_port);
		execv("receiver", buf);
	}

	/**
	 * Run SENDER.
	 */
	if ((receiver_pid = Process::Create()) == 0)
	{
		char* buf[] = { "sender", 0, 0, 0 };
		buf[1] = (char*) malloc(32 * sizeof(char));
		buf[2] = (char*) malloc(32 * sizeof(char));
		sprintf(buf[1], "%d", segment_id);
		sprintf(buf[2], "%d", sem_id);
		execv("sender", buf);
	}


	/**
	 * Wait for children.
	 */
	int status;
	status = Process::Wait();
	status = Process::Wait();
	status = Process::Wait();
	status = Process::Wait();

	DEBUGS("init: all children exited");

	/**
	 * Destroy semaphore alloceted for processes
	 * synchronization.
	 */
	BinarySemaphore::Destroy(sem_id);

	/**
	 * Detach shared memory segment and 
	 * deallocate shared memory.
	 */
	shmdt(shared_memory);
	shmctl(segment_id, IPC_RMID, NULL);

	return 0;
}
